Ext.onReady(function () {

  // main window
  var win = new Ext.Window({
    width : 800,
    title : 'JSONP Tester (powered by Ext JS & jQuery)',
    border : false,
    autoHeight : true,
    closable : false,
    layout : 'anchor',
    defaults : { anchor : '100%' },
    items : [{
      id : 'jsonpForm',
      xtype : 'form',
      title : 'JSONP Parameters',
      frame : true,
      defaultType : 'textfield',
      labelWidth : 150,
      items : [{ 
        name : 'url', fieldLabel : 'JSONP Service URL', anchor : '100%' 
      }, {
        xtype : 'fieldset',
        title : 'Optional Parameters',
        collapsible : true,
        collapsed : true,
        autoHeight : true,
        items : [{
          xtype : 'textfield',
          name : 'callbackParam', 
          fieldLabel : 'Callback Parameter Name',
          value : 'callback' 
        }, {
          xtype : 'numberfield',
          name : 'timeout', 
          fieldLabel : 'Timeout (msec)',
          value : 15000 
        }, {
          xtype : 'checkbox',
          name : 'nocache', 
          fieldLabel : 'Append No Cache Param',
          checked : true
        }]
      }],
      buttons : [{
        text : 'Request',
        handler : requestJSONP
      }]
    }, {
      id : 'resultArea',
      title : 'JSONP Result',
      layout : 'border',
      height : 400,
      items : [{
        id : 'resultContainer',
        region : 'center',
        border : false,
        layout : 'fit'
      }, {
        id : 'resultDump',
        region : 'east',
        title : 'JSON Dump',
        width : '200',
        collapsible : true,
        split : true,
        html : '<textarea id="dumparea" style="width:100%;height:100%;"></textarea>'
      }]
    }] 
  });

  win.show();

  var form = Ext.getCmp('jsonpForm').getForm();
  var resultContainer = Ext.getCmp('resultContainer');
  // JSONP parameters can be sent from outside via url hash.
  // format 
  //    #<callbackParam>:<jsonpServiceURL>
  var m = location.href.match(/#(.*):(.*)$/);
  if (m) form.setValues({ callbackParam : m[1], url : decodeURIComponent(m[2]) });


  var resultDump = Ext.getCmp('resultDump');
  var resultTree;

  function requestJSONP() {
    // remove resulttree if already created;
    if (resultTree) {
      resultContainer.remove(resultTree, true);
      resultTree = null;
    }

    var values = form.getValues();

    resultTree = resultContainer.add({
      xtype : 'treepanel',
      autoScroll : true,
      root : new Ext.tree.AsyncTreeNode({
        text : 'result',
        url : values.url,
        callbackParam : values.callbackParam == '' ? 'callback' : values.callbackParam,
        timeout : + values.timeout, // force cast string to number
        nocache : values.nocache == 'on'
      }),
      loader : new JsonpLoader()
    });

    resultContainer.doLayout();

    resultTree.getRootNode().expand();

    resultTree.on('click', function(node, e) {
      if (resultDump.isVisible()) {
        $('#dumparea').attr('value', Ext.util.JSON.encode(node.attributes.value));
      }
    });
  }

/**
 * @class AsyncLoader
 * @extends Ext.tree.TreeLoader
 */
var AsyncLoader = Ext.extend(Ext.tree.TreeLoader, {
  dataUrl : '#',

  /* abstract */
  doAsyncLoad : Ext.emptyFn,

  // overriding TreeLoader's methods
  requestData : function(node, callback){
    if (this.fireEvent("beforeload", this, node, callback) !== false){
      var _this = this;
      this.doAsyncLoad(node, {
        onSuccess: function(results) { _this.handleResponse(results, node, callback) },
        onFailure: function(error) { _this.handleFailure(error, node, callback) }
      })
    } else {
      // if the load is cancelled, make sure we notify
      // the node that we are done
      if (typeof callback == "function"){
        callback();
      }
    }
  },

  handleResponse : function(results, node, callback){
    try {
      node.beginUpdate();
      for(var i=0, len=results.length; i<len; i++){
        var n = this.createNode(results[i]);
        if(n){
          node.appendChild(n);
        }
      }
      node.endUpdate();
      if (typeof callback == "function"){
        callback(this, node);
      }
      this.fireEvent("load", this, node, results);
    } catch(e) {
      this.handleFailure(e);
    }
  },

  handleFailure : function(error, node, callback){
    this.fireEvent("loadexception", this, node, error);
    if (typeof callback == "function"){
      callback(this, node);
    }
  }

});


/**
 * @class JsonpLoader 
 * @extends AsyncLoader
 */
var JsonpLoader = Ext.extend(AsyncLoader, {

  doAsyncLoad : function(node, callback) {
    if (!node.attributes.value) {
      // using jQuery JSONP call
      $.ajax({
        url : node.attributes.url,
        dataType : 'jsonp',
        jsonp : node.attributes.callbackParam,
        cache : !node.attributes.nocache,
        success : function(o) { 
          node.attributes.value = o;
          callback.onSuccess(describeProperties(o));
        },
        error : callback.onFailure
      });
    } else {
      callback.onSuccess(describeProperties(node.attributes.value))
    }

    function describeProperties(o) {
      var ret = [];
      if (o && o.constructor == Array) {
        for (var i=0,len=o.length; i<len; i++) {
          ret.push(createNodeConfig('['+i+']', o[i]));
        }
      } else if (typeof o == "object" && o != null) {
        for (var key in o) {
          ret.push(createNodeConfig(key, o[key]));
        }
      }
      return ret;
    } 

    function createNodeConfig(name, value) {
      var leaf = (value == null || 
                  value.constructor == String || value.constructor == Boolean ||
                  value.constructor == Number || value.constructor == Date);
      return {
        text : leaf ? name + ' : ' + value : name,
        value : value,
        leaf : leaf 
      };
    }
  }

});


});
